package chargemanager

import (
	"context"
	"errors"
	"sync"
	"time"

	"dev.ixmxm.top/rms/boot"
	"dev.ixmxm.top/rms/codes"
	"dev.ixmxm.top/rms/managers/robotmanager"
	"dev.ixmxm.top/rms/managers/trafficmanager"
	"dev.ixmxm.top/rms/model"
	"dev.ixmxm.top/rms/utils/function"
	"dev.ixmxm.top/rms/utils/log"
	"dev.ixmxm.top/rms/utils/tools"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

func New(db *boot.MongoDB) *ChargeManager {
	manager := &ChargeManager{db: db}
	manager.loadAllChargerFromDB()
	return manager
}

type ChargeManager struct {
	db           *boot.MongoDB
	charges      sync.Map
	SystemConfig *model.SystemConfig //系统配置(缓存),不可直接赋值保存，调用SaveSystemConfigToCache保存
}

//AddCharges 增加一个充电点
func (manager *ChargeManager) AddCharger(id uint8, c *Charger) {
	manager.charges.Store(id, c)
}

//AddCharges 增加一个充电点
func (manager *ChargeManager) DeleteCharger(id uint8) {
	manager.charges.Delete(id)
}

//LoadCharger 加载charger
func (manager *ChargeManager) LoadCharger(id uint8) (c *Charger, isOk bool) {
	chargerO, ok := manager.charges.Load(id)
	if !ok {
		return nil, false
	}

	return chargerO.(*Charger), true

}

//LoadCharger 通过agvID加载充电站
func (manager *ChargeManager) LoadChargerByAgvID(agvID string) (c *Charger, isOk bool) {
	var charger *Charger
	manager.charges.Range(func(key, value interface{}) bool {
		charger = value.(*Charger)
		if charger.info.AgvID != agvID {
			return true
		}
		isOk = true
		return false
	})

	return charger, isOk
}

//AddChargePointToDB 添加替换充电点到数据库
func (manager *ChargeManager) AddChargePointToDB(ctx context.Context, id uint8, mapName, siteID string, ip string, port int, banAgvList []string) codes.Code {
	chargeSite := model.ChargeSite{
		Id:         id,
		MapName:    mapName,
		SiteId:     siteID,
		Ip:         ip,
		Port:       port,
		AgvID:      "",
		BanAgvList: banAgvList,
	}
	dbRes := manager.db.Collection(ctx, model.ChargeSite{}.TableName()).FindOneAndReplace(ctx, bson.M{"id": id}, chargeSite, &options.FindOneAndReplaceOptions{Upsert: function.BoolPtr(true)})
	if dbRes.Err() != nil && !errors.Is(dbRes.Err(), mongo.ErrNoDocuments) {
		log.ErrorfWithContext(ctx, "add charge site to db error:%s", dbRes.Err().Error())
		return codes.ErrDatabase.Wrap(dbRes.Err())
	}

	charger, ok := manager.LoadCharger(id)
	if !ok {
		manager.AddCharger(id, newCharger(chargeSite))
	} else {
		charger.info = chargeSite
		go charger.init()
	}
	return nil
}

//DeleteChargePointFromDB 从数据库删除充电点
func (manager *ChargeManager) DeleteChargePointFromDB(ctx context.Context, id uint8) codes.Code {
	Charger := manager.GetChargersFromCache(ctx, id)
	if Charger == nil {
		return codes.ErrChargerNotFound
	}
	if Charger.info.AgvID != "" {
		return codes.ErrChargeLocked
	}
	_, err := manager.db.Collection(ctx, model.ChargeSite{}.TableName()).DeleteOne(ctx, bson.M{"id": id})
	if err != nil && !errors.Is(err, mongo.ErrNoDocuments) {
		log.ErrorfWithContext(ctx, "delete charge site from db error:%s", err.Error())
		return codes.ErrDatabase.Wrap(err)
	}

	charger, ok := manager.LoadCharger(id)
	if !ok {
		log.WarnfWithContext(ctx, "charger %d has been delete", id)
		return nil
	}

	if charger.cancelFunc != nil {
		charger.cancelFunc()
	}
	charger.isStop = true
	manager.DeleteCharger(id)
	return nil
}

//DeleteMapChargePointFromDB 从数据库删除充电点
func (manager *ChargeManager) DeleteMapChargePointFromDB(ctx context.Context, mapName string) codes.Code {
	_, err := manager.db.Collection(ctx, model.ChargeSite{}.TableName()).DeleteOne(ctx, bson.M{"map_name": mapName})
	if err != nil && !errors.Is(err, mongo.ErrNoDocuments) {
		log.ErrorfWithContext(ctx, "delete charge site from db error:%s", err.Error())
		return codes.ErrDatabase.Wrap(err)
	}

	manager.charges.Range(func(key, value interface{}) bool {
		charger := value.(*Charger)
		if charger.info.MapName == mapName {
			if charger.cancelFunc != nil {
				charger.cancelFunc()
			}
			manager.DeleteCharger(key.(uint8))
		}
		return true
	})

	return nil
}

//GetAllChargerPointsFromDB 从数据库获得所有充电点
func (manager *ChargeManager) GetAllChargerPointsFromDB(ctx context.Context) ([]model.ChargeSite, codes.Code) {
	sites := make([]model.ChargeSite, 0)
	res, err := manager.db.Collection(ctx, model.ChargeSite{}.TableName()).Find(ctx, bson.M{})
	if err != nil {
		log.ErrorfWithContext(ctx, "get all charge sites from db error:%s", err.Error())
		return nil, codes.ErrDatabase.Wrap(err)
	}
	if errBind := res.All(ctx, &sites); errBind != nil {
		log.ErrorfWithContext(ctx, "bind all charge sites error:%s", errBind.Error())
		return nil, codes.ErrDatabase.Wrap(errBind)
	}
	return sites, nil
}

//GetAllChargersFromCache 获得缓存所有充电桩
func (manager *ChargeManager) GetAllChargersFromCache(ctx context.Context) map[uint8]*Charger {
	chargerMap := make(map[uint8]*Charger)
	manager.charges.Range(func(key, value interface{}) bool {
		c := value.(*Charger)
		chargerMap[key.(uint8)] = c
		return true
	})
	return chargerMap
}

func (manager *ChargeManager) GetAllChargersFromCachebyAgvId(agvId string) []*Charger {
	chargerList := make([]*Charger, 0)
	manager.charges.Range(func(key, value interface{}) bool {
		c := value.(*Charger)
		if tools.StringSliceIsContain(c.GetInfo().BanAgvList, agvId) {
			chargerList = append(chargerList, c)
		}
		return true

	})
	return chargerList
}

//GetAllChargersFromCache 获得充电桩
func (manager *ChargeManager) GetChargersFromCache(ctx context.Context, chargerId uint8) *Charger {
	charger, _ := manager.LoadCharger(chargerId)
	return charger
}

//判断是否还存在空闲充电桩（也就是还没有绑定AGV 编号的充电桩）
func (manager *ChargeManager) IsHaveFreeCharger() bool {
	isHave := false
	manager.charges.Range(func(key, value interface{}) bool {
		charger := value.(*Charger)
		if charger.info.AgvID == "" || charger.info.AgvID == "string" {
			isHave = true
			return false
		}
		return true
	})

	return isHave
}

//MallocCharger 申请最优充电桩
func (manager *ChargeManager) MallocCharger(ctx context.Context, robot *model.Robot, chargerID uint8, robotMaps []string, trm *trafficmanager.TrafficManager, elevators map[string]*model.Elevator, isFindRule bool, rule model.DayChargerRule, chargerRule1 model.ChargeRule) *Charger {
	inRobotMapList := func(map_name string) bool {
		for _, m := range robotMaps {
			if m == map_name {
				return true
			}
		}
		return false
	}

	var (
		bestCharger *Charger
		minDis      float64
	)
	manager.charges.Range(func(key, value interface{}) bool {
		charger := value.(*Charger)

		if chargerID != 0 && chargerID != charger.info.Id {
			return true
		}
		if charger.GetChargerStatus() != ChargerOK {
			//log.Warnf("charger:%d is not ok:%d", charger.info.Id, charger.GetChargerStatus())
			return true
		}
		if charger.status.IsCharging {
			//log.Warnf("charger:%d is charging", charger.info.Id)
			return true
		}
		if charger.info.AgvID == robot.Account.AgvId {
			return true
		}
		if charger.info.AgvID != "" {
			//log.Warnf("charger:%d is busy for another robot:%s", charger.info.Id, charger.info.AgvID)
			return true
		}
		//判断充电桩是否禁用此AGV
		if len(charger.info.BanAgvList) > 0 {
			agvIsBan := false
			for _, banAgv := range charger.info.BanAgvList {
				if banAgv == robot.Account.AgvId {
					agvIsBan = true
					break
				}
			}
			if agvIsBan {
				return true
			}
		}
		//厂区校验
		if !robot.StatusIsExists() {
			return true
		}

		if charger.info.MapName == robot.Status.MapName {
			gPath, _, errGPlan := trm.GlobalPathPlan(ctx, robot, charger.info.MapName, robot.Status.SiteIdS, charger.info.SiteId)
			if errGPlan != nil {
				return true
			}
			if bestCharger == nil || gPath.Cost < minDis {
				bestCharger = charger
				minDis = gPath.Cost
			}
		} else if inRobotMapList(charger.info.MapName) { //多楼层充电任务生成
			_, cost, err := trm.GetCrossPath(ctx, robot, charger.info.MapName, charger.info.SiteId, elevators, nil)
			if err != nil {
				return true
			}
			if bestCharger == nil || cost < minDis {
				bestCharger = charger
				minDis = cost
			}
		}
		return true
	})

	//修改充电桩占用
	if bestCharger != nil {

		_, err := manager.db.Collection(ctx, model.ChargeSite{}.TableName()).UpdateOne(ctx, bson.M{"id": bestCharger.info.Id, "agv_id": ""}, bson.M{"$set": bson.M{"agv_id": robot.Account.AgvId, "rule": chargerRule1}})
		if err != nil {
			log.ErrorfWithContext(ctx, "update charger status error:%s", err.Error())
			return nil
		}
		commit := func() {
			bestCharger.info.AgvID = robot.Account.AgvId
			bestCharger.info.Rule = chargerRule1
			//
			//bestCharger.info.Uuid = uuid
		}
		rollback := func() {

		}
		boot.TryAddTx(ctx, commit, rollback)
	}
	return bestCharger
}

//获取可打断充电的充电桩
func (manager *ChargeManager) GetBreakCharger(ctx context.Context, robot *model.Robot, chargerID uint8, trm *trafficmanager.TrafficManager) *Charger {
	charger, ok := manager.LoadCharger(chargerID)
	if !ok {
		return nil
	}

	if charger.GetChargerStatus() != ChargerOK {
		log.Warnf("charger:%d is not ok:%d", charger.info.Id, charger.GetChargerStatus())
		return nil
	}
	if charger.info.AgvID == robot.Account.AgvId {
		return charger
	}
	//厂区校验
	if !robot.StatusIsExists() || charger.info.MapName != robot.Status.MapName {
		return nil
	}
	if charger.status.IsCharging && charger.info.AgvID != "" {
		chargingRobot := robotmanager.GetFromCache(charger.info.AgvID)
		if !chargingRobot.StatusIsExists() || chargingRobot.Status.Power <= chargingRobot.Type.ChargeToTaskPercent {
			return nil
		}
		return charger
	}

	return nil
}

//FreeCharger 释放充电桩
func (manager *ChargeManager) FreeCharger(ctx context.Context, mapName, siteID string, id uint8) codes.Code {

	charger, find := manager.LoadCharger(id)
	if !find {
		return nil
	}
	_, err := manager.db.Collection(ctx, model.ChargeSite{}.TableName()).UpdateOne(ctx, bson.M{"map_name": mapName, "site_id": siteID, "id": id, "agv_id": bson.M{"$ne": ""}}, bson.M{"$set": bson.M{"agv_id": ""}})
	if err != nil {
		log.ErrorfWithContext(ctx, "update charger status error:%s", err.Error())
		return codes.ErrDatabase.Wrap(err)
	}
	commit := func() {
		log.Infof("(freecharger)释放充电桩:%d robot:%s ", charger.info.Id, charger.info.AgvID)
		if charger.info.AgvID != "" {
			charger.finishCharge()
		}
		charger.info.AgvID = ""
		//manager.charges[id].info.Uuid = ""
	}
	rollback := func() {}
	boot.TryAddTx(ctx, commit, rollback)
	return nil
}

//小车开始充电（发送充电信号、统计充电时间）
func (manager *ChargeManager) StartRobotCharger(ctx context.Context, subtask *model.Subtask) codes.Code {

	charger, ok := manager.LoadChargerByAgvID(subtask.AgvId)
	if !ok {
		return codes.ErrChargerNotFound
	}

	errStart := manager.statisticStartCharge(ctx, subtask.AgvId, subtask.TaskId)
	if errStart != nil {
		log.Warnf("小车开始充电,记录统计开始时间出错 robot:%s err:%s", subtask.AgvId, errStart.UnWrapError())
		return errStart
	}
	if errStart = charger.startCharge(); errStart != nil {
		log.Warnf("小车开始充电,发送充电信号出错  robot:%s err:%s", subtask.AgvId, errStart.UnWrapError())
		return errStart
	}
	return nil
}

//FinishCharger 释放机器人占用的充电桩（发送断电信号、统计充电时间、释放充电桩）
func (manager *ChargeManager) FinishCharger(ctx context.Context, taskId uint64, agvID string) codes.Code {
	dbRes := manager.db.Collection(ctx, model.ChargeSite{}.TableName()).FindOneAndUpdate(ctx, bson.M{"agv_id": agvID}, bson.M{"$set": bson.M{"agv_id": ""}})
	if dbRes.Err() != nil && !errors.Is(dbRes.Err(), mongo.ErrNoDocuments) {
		log.ErrorfWithContext(ctx, "update charger status error:%s", dbRes.Err().Error())
		return codes.ErrDatabase.Wrap(dbRes.Err())
	}
	if dbRes.Err() != nil && errors.Is(dbRes.Err(), mongo.ErrNoDocuments) {
		return nil
	}
	chargerSite := new(model.ChargeSite)
	if errBind := dbRes.Decode(chargerSite); errBind != nil {
		log.ErrorfWithContext(ctx, "can not bind charge site data from db:%s", errBind.Error())
		return codes.ErrDatabase.Wrap(errBind)
	}
	charger, find := manager.LoadCharger(chargerSite.Id)
	if !find {
		return codes.ErrChargerNotFound
	}

	manager.statisticFinishCharge(ctx, taskId, agvID)
	charger.finishCharge()
	charger.info.AgvID = ""
	log.Infof("释放充电桩:%d", charger.info.Id)
	return nil
}

//充电桩发送缩回信号
func (manager *ChargeManager) RetractCharger(chargerId uint8) codes.Code {
	charger, find := manager.LoadCharger(chargerId)
	if !find {
		return nil
	}
	code := charger.finishCharge()
	if code == nil {
		log.Infof("充电桩发送缩回信号:%d", chargerId)
	}
	return code
}

//充电桩发送伸出信号(记录伸出次数)
func (manager *ChargeManager) StretchCharger(ctx context.Context, taskId uint64, agvID string) codes.Code {

	//记录伸出次数
	err := manager.StatisticChargeNumAdd(ctx, taskId, agvID)
	if err != nil {
		log.Warnf("小车开始充电,记录伸出次数出错  robot:%s err:%s", agvID, err.UnWrapError())
		return err
	}

	charger, ok := manager.LoadChargerByAgvID(agvID)
	if !ok {
		return codes.ErrChargerNotFound
	}
	//开始发送伸出信号
	if err = charger.startCharge(); err != nil {
		log.Warnf("小车开始充电,发送充电信号出错  robot:%s err:%s", agvID, err.UnWrapError())
		return err
	}
	log.Infof("充电桩发送伸出信号成功 robot:%s", agvID)
	return nil

}

// //结束小车充电（发送断电信号、统计充电时间、释放充电桩）
// func (manager *ChargeManager) FinishRobotCharger(ctx context.Context, agvID string) codes.Code {
// 	dbRes := manager.db.Collection(ctx, model.ChargeSite{}.TableName()).(ctx, bson.M{"agv_id": agvID}, bson.M{"$set": bson.M{"agv_id": ""}})
// 	if dbRes.Err() != nil && !errors.Is(dbRes.Err(), mongo.ErrNoDocuments) {
// 		log.ErrorfWithContext(ctx, "update charger status error:%s", dbRes.Err().Error())
// 		return codes.ErrDatabase.Wrap(dbRes.Err())
// 	}
// 	if dbRes.Err() != nil && errors.Is(dbRes.Err(), mongo.ErrNoDocuments) {
// 		return nil
// 	}
// 	chargerSite := new(model.ChargeSite)
// 	if errBind := dbRes.Decode(chargerSite); errBind != nil {
// 		log.ErrorfWithContext(ctx, "can not bind charge site data from db:%s", errBind.Error())
// 		return codes.ErrDatabase.Wrap(errBind)
// 	}
// 	manager.chargersLock.Lock()
// 	defer manager.chargersLock.Unlock()
// 	charger, find := manager.charges[chargerSite.Id]
// 	if !find {
// 		return codes.ErrChargerNotFound
// 	}
// 	commit := func() {
// 		defer log.Infof("释放机器人:%s充电桩:%d", agvID, charger.info.Id)
// 		manager.statisticFinishCharge(ctx, charger.info.Uuid)
// 		charger.finishCharge()
// 		charger.info.AgvID = ""
// 		charger.info.Uuid = ""
// 	}
// 	rollback := func() {}
// 	boot.TryAddTx(ctx, commit, rollback)
// 	return nil
// }

//统计充电信息
func (manager *ChargeManager) GetChargeStatistic(ctx context.Context, agvID string, from, end time.Time) ([]model.ChargeStatistic, codes.Code) {
	if end.IsZero() {
		end = time.Now()
	}
	filter := bson.M{"start_time": bson.M{"$gte": from, "$lte": end}}
	if agvID != "" {
		filter["agv_id"] = agvID
	}
	dbRes, err := manager.db.Collection(ctx, model.ChargeStatistic{}.TableName()).Find(ctx, filter)
	if err != nil {
		log.ErrorfWithContext(ctx, "get charge statistics from db error:%s", err.Error())
		return nil, codes.ErrDatabase.Wrap(err)
	}
	statistics := make([]model.ChargeStatistic, 0)
	if errBind := dbRes.All(ctx, &statistics); errBind != nil {
		log.ErrorfWithContext(ctx, "bind charge statistics from db error:%s", errBind.Error())
		return nil, codes.ErrDatabase.Wrap(errBind)
	}
	return statistics, nil
}

//记录统计充电开始时间
func (manager *ChargeManager) statisticStartCharge(ctx context.Context, agvID string, taskId uint64) codes.Code {
	statistic := &model.ChargeStatistic{
		AGVId:     agvID,
		StartTime: time.Now(),
		TotalTime: 0,
		//Uuid:      uuid,
		TaskId:    taskId,
		ChargeNum: 1,
	}
	_, err := manager.db.Collection(ctx, model.ChargeStatistic{}.TableName()).InsertOne(ctx, statistic)
	if err != nil {
		log.ErrorfWithContext(ctx, "statistic start charge error:%s", err.Error())
		return codes.ErrDatabase.Wrap(err)
	}
	return nil
}

//记录统计充电结束时间
func (manager *ChargeManager) statisticFinishCharge(ctx context.Context, taskId uint64, agvId string) codes.Code {
	statistic, err := manager.GetStatisticCharge(ctx, taskId, agvId)
	if err != nil || statistic == nil {
		log.ErrorfWithContext(ctx, "statistic finish charge error,bind data error:%s", err.Error())
		return codes.ErrDatabase.Wrap(err)
	}
	dbRes := manager.db.Collection(ctx, model.ChargeStatistic{}.TableName()).FindOneAndUpdate(ctx, bson.M{"task_id": taskId, "agv_id": agvId}, bson.M{"$set": bson.M{"end_time": time.Now(), "total_time": time.Now().Sub(statistic.StartTime).Hours()}})
	if dbRes.Err() != nil {
		log.ErrorfWithContext(ctx, "statistic finish charge error:%s", dbRes.Err().Error())
		return codes.ErrDatabase.Wrap(dbRes.Err())
	}
	return nil
}

//获取单条充电统计信息
func (manager *ChargeManager) GetStatisticCharge(ctx context.Context, taskId uint64, agvId string) (*model.ChargeStatistic, codes.Code) {
	dbRes := manager.db.Collection(ctx, model.ChargeStatistic{}.TableName()).FindOne(ctx, bson.M{"task_id": taskId, "agv_id": agvId})
	if dbRes.Err() != nil {
		log.Warnf("获取单条充电统计信息错误 error:%s", dbRes.Err().Error())
		return nil, codes.ErrDatabase.Wrap(dbRes.Err())
	}
	statistic := new(model.ChargeStatistic)
	if err := dbRes.Decode(statistic); err != nil {
		log.Warnf("获取单条充电统计信息错误,bind data error:%s", err.Error())
		return nil, codes.ErrDatabase.Wrap(err)
	}
	return statistic, nil
}

//统计充电次数增加
func (manager *ChargeManager) StatisticChargeNumAdd(ctx context.Context, taskId uint64, agvId string) codes.Code {
	log.Infof("统计充电次数增加")
	statistic, err := manager.GetStatisticCharge(ctx, taskId, agvId)
	if err != nil || statistic == nil {
		log.ErrorfWithContext(ctx, "统计充电次数增加错误,bind data error:%s", err.Error())
		return codes.ErrDatabase.Wrap(err)
	}
	log.Infof("统计充电次数增加 robot:%s taskid:%d", statistic.AGVId, statistic.TaskId)
	dbRes := manager.db.Collection(ctx, model.ChargeStatistic{}.TableName()).FindOneAndUpdate(ctx, bson.M{"task_id": taskId, "agv_id": agvId}, bson.M{"$set": bson.M{"charge_num": statistic.ChargeNum + 1}})
	if dbRes.Err() != nil {
		log.ErrorfWithContext(ctx, "统计充电次数增加错误:%s", dbRes.Err().Error())
		return codes.ErrDatabase.Wrap(dbRes.Err())
	}
	return nil
}

func (manager *ChargeManager) loadAllChargerFromDB() {
	chargeSites := make([]model.ChargeSite, 0)
	res, err := manager.db.Collection(context.Background(), model.ChargeSite{}.TableName()).Find(context.Background(), bson.M{})
	if err != nil {
		log.Panicf("load all chargers error,can not load from db:%s", err.Error())
		return
	}
	if errBind := res.All(context.Background(), &chargeSites); errBind != nil {
		log.Panicf("load all chargers error,can not bind data from db:%s", errBind.Error())
		return
	}

	for _, info := range chargeSites {
		manager.AddCharger(info.Id, newCharger(info))
	}
}
